﻿namespace System.Dynamic
{
	using System.Collections;
	using System.Collections.Generic;
	using System.Linq;
	using System.Reflection;

	/// <summary>
	/// Represents a proxy that handles multiple objects as one, performs every operation on each object.
	/// </summary>
	internal class MulticastObject : DynamicObject
	{
		public MulticastObject(params object[] objects)
		{
			if (objects == null)
				throw new ArgumentNullException("objects");

			if (!objects.Any())
				throw new ArgumentException("The provided collection of objects is empty.");

			this.objects = objects;
		}

		IEnumerable objects;

		public override bool TrySetMember(SetMemberBinder binder, object value)
		{
			bool hasSet = false;

			// set the value on each object
			foreach (var o in objects)
			{
				// try resolve property by name
				PropertyInfo pi = o.GetType().GetProperty(binder.Name, BindingFlags.Public | BindingFlags.Instance);

				if (pi != null)
				{
					pi.SetValue(o, value, null);
					hasSet = true;
				}

				// try resolve field by name
				FieldInfo fi = o.GetType().GetField(binder.Name, BindingFlags.Public | BindingFlags.Instance);

				if (fi != null)
				{
					fi.SetValue(o, value);
					hasSet = true;
				}
			}

			return hasSet;
		}

		public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
		{
			bool hasSet = false;

			// set the value on each object
			foreach (var o in objects)
			{
				// resolve default indexer property
				PropertyInfo pi = o.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance);

				if (pi != null)
				{
					pi.SetValue(o, value, indexes);
					hasSet = true;
				}
			}

			return hasSet;
		}

		public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
		{
			bool hasInvoked = false;

			// try invoke each object
			foreach (var o in objects)
			{
				Delegate @delegate = o as Delegate;

				if (@delegate != null)
				{
					try
					{
						@delegate.DynamicInvoke(args);
						hasInvoked = true;
					}
					catch { }
				}
			}

			result = null;
			return hasInvoked;
		}

		public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
		{
			bool hasInvoked = false;

			// set the value on each object
			foreach (var o in objects)
			{
				// resolve method by name and argument types
				MethodInfo mi = o.GetType().GetMethod(binder.Name, args.Select(a => a.GetType()).ToArray());

				if (mi != null)
				{
					if (mi.ReturnType != typeof(void))
						throw new InvalidOperationException("Methods with non-void return type are not allowed.");

					mi.Invoke(o, args);
					hasInvoked = true;
				}
			}

			result = null;
			return hasInvoked;
		}

		public override IEnumerable<string> GetDynamicMemberNames()
		{
			return objects.Cast<object>().Select(o => o.GetType())
				.SelectMany(t => t.GetMembers(BindingFlags.Public | BindingFlags.Instance))
				.Select(m => m.Name).Distinct();
		}
	}
}
